package org.dyndns.opendemogroup.optimizer.problems;

import java.util.Random;

import org.dyndns.opendemogroup.optimizer.GenericGenerationalGeneticAlgorithm;
import org.dyndns.opendemogroup.optimizer.ICustomCoding;
import org.dyndns.opendemogroup.optimizer.ICustomInitialization;
import org.dyndns.opendemogroup.optimizer.IOptimizationProblem;
import org.dyndns.opendemogroup.optimizer.Member;
import org.dyndns.opendemogroup.optimizer.OptimizationProfile;
import org.dyndns.opendemogroup.optimizer.Population;
import org.dyndns.opendemogroup.optimizer.RunResult;

/**
 * A special IOptimizationProblem implementation meant for running an Evolution
 * Strategy (ES) in nested mode as an ES itself.
 */
public class NestedEvolutionStrategy implements IOptimizationProblem,
		ICustomInitialization, ICustomCoding
{

	// { fields
	private final int memberSize;

	private final boolean isMaximizing;

	private final OptimizationProfile innerProfile;

	private final GenericGenerationalGeneticAlgorithm ggga;

	private Population innerPopulation;

	// }

	public NestedEvolutionStrategy ( OptimizationProfile profile )
	{
		innerProfile = profile;
		memberSize = profile.Problem.getMemberSize ( ) * profile.PopulationSize;
		isMaximizing = profile.Problem.isMaximizing ( );
		ggga = new GenericGenerationalGeneticAlgorithm ( profile );
	}

	// { IOptimizationProblem implementation
	public double computeFitness ( Random randomSource )
	{
		ggga.setRandomSource ( randomSource );
		// have the engine perform a single run according to profile
		RunResult result = ggga.run ( innerPopulation );

		// the fitness of a population is the fitness of the best item
		double fitness = result.BestFitness;
		return fitness;
	}

	public int getGeneRange ( )
	{
		assert false : "This method should not be called.";
		return 0;
	}

	public int getMemberSize ( )
	{
		return memberSize;
	}

	public boolean isCombinatorialOptimization ( )
	{
		return false;
	}

	public boolean isMaximizing ( )
	{
		return isMaximizing;
	}

	public boolean makeValid ( )
	{
		// do nothing on purpose, but return true because the fitness
		// computation most DEFINITELY alters the genome!
		return true;
	}

	// }

	// { ICustomInitialization implementation
	public void initialize ( Random randomSource, Population population )
	{
		if ( innerProfile.Problem instanceof ICustomInitialization )
		{
			ICustomInitialization init =
				(ICustomInitialization) innerProfile.Problem;
			if ( null == innerPopulation )
			{
				innerPopulation =
					new Population ( innerProfile.PopulationSize,
						innerProfile.Problem.getMemberSize ( ) );
			}
			init.initialize ( randomSource, innerPopulation );
			for ( int op = 0; op < population.getPopulationSize ( ); op++ )
			{

				// encode innerPop as a single genome in the outer population
				encode ( innerPopulation, population.members[op].genome );
			}
		}
		else
		{
			throw new IllegalArgumentException (
				"Umm...  The NestedEvolutionStrategy does not know how to cope "
						+ "with one of them IOptimizationProblem "
						+ "implementations which does not also implement the "
						+ "ICustomInitialization interface.  Oh, and don't "
						+ "forget the cover letters on the TPS reports." );
		}
	}

	// }

	static void decode ( double[] sourceGenome, Population population )
	{
		int sourceIndex = 0;
		final int innerPopSize = population.getPopulationSize ( );
		final int innerMemberSize = population.getMemberSize ( );
		for ( int ip = 0; ip < innerPopSize; ip++ )
		{
			final double[] innerGenome = population.members[ip].genome;
			for ( int g = 0; g < innerMemberSize; g++ )
			{
				innerGenome[g] = sourceGenome[sourceIndex];
				sourceIndex++;
			}
		}
	}

	static void encode ( Population population, double[] targetGenome )
	{
		int targetIndex = 0;
		final int innerPopSize = population.getPopulationSize ( );
		final int innerMemberSize = population.getMemberSize ( );
		for ( int ip = 0; ip < innerPopSize; ip++ )
		{
			final double[] innerGenome = population.members[ip].genome;
			for ( int g = 0; g < innerMemberSize; g++ )
			{
				targetGenome[targetIndex] = innerGenome[g];
				targetIndex++;
			}
		}
	}

	// { ICustomCoding implementation
	public void decodeFrom ( Member source )
	{
		decode ( source.genome, innerPopulation );
	}

	public void encodeTo ( Member target )
	{
		encode ( innerPopulation, target.genome );
	}
	// }

}
